package com.zjy.log.aspect;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.zjy.common.constant.StatusEnums;
import com.zjy.common.domain.dto.RequestInfoDto;
import com.zjy.common.domain.model.RequestHolder;
import com.zjy.common.utils.ConvertBeanUtils;
import com.zjy.common.utils.ServletUtils;
import com.zjy.common.utils.SpringUtils;
import com.zjy.log.annotation.LogDataDto;
import com.zjy.log.annotation.OperationLog;
import com.zjy.log.event.OperationLogEvent;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.boot.autoconfigure.AutoConfiguration;

import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author zhangjianyu
 * email: 578547270@qq.com
 * create_time: 2024-10-23 09:48
 * description:
 */
@Slf4j
@Aspect
@AutoConfiguration
public class OperationLogAspect {

    /**
     * 计时 key
     */
    private static final ThreadLocal<LogDataDto> KEY_CACHE = new ThreadLocal<>();

    /**
     * 处理请求前执行
     */
    @Before(value = "@annotation(controllerLog)")
    public void doBefore(JoinPoint joinPoint, OperationLog controllerLog) {
        StopWatch stopWatch = new StopWatch();
        RequestInfoDto info = ConvertBeanUtils.convert(RequestHolder.get(), RequestInfoDto.class);
        stopWatch.start();
        LogDataDto dataDto = LogDataDto.builder()
                .info(info)
                .stopWatch(stopWatch)
                .build();
        KEY_CACHE.set(dataDto);
    }

    /**
     * 处理完请求后执行
     *
     * @param joinPoint 切点
     */
    @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
    public void doAfterReturning(JoinPoint joinPoint, OperationLog controllerLog, Object jsonResult) {
        handleLog(joinPoint, controllerLog, null, jsonResult);
    }

    /**
     * 拦截异常操作
     *
     * @param joinPoint 切点
     * @param e         异常
     */
    @AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, OperationLog controllerLog, Exception e) {
        handleLog(joinPoint, controllerLog, e, null);
    }

    /**
     * 处理操作日志
     *
     * @param joinPoint 切入点，用于获取方法相关信息
     * @param controllerLog 控制器日志注解，用于获取方法描述等信息
     * @param e 异常信息，用于记录错误日志
     * @param jsonResult 方法执行结果，用于记录操作结果
     */
    private void handleLog(final JoinPoint joinPoint, OperationLog controllerLog, final Exception e, Object jsonResult) {
        try {
            OperationLogEvent operationEvent = new OperationLogEvent();
            // *========数据库日志=========*//
            if (e != null) {
                operationEvent.setStatus(StatusEnums.PUBLIC_STATUS_STOP.getStatus());
                operationEvent.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
            } else {
                operationEvent.setStatus(StatusEnums.PUBLIC_STATUS_STOP.ordinal());
            }
            // 请求的地址
            operationEvent.setIp(ServletUtils.getClientIP());
            operationEvent.setUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255));

            // 设置方法名称
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            operationEvent.setMethod(className + "." + methodName + "()");
            // 设置请求方式
            operationEvent.setRequestMethod(ServletUtils.getRequest().getMethod());
            // 处理设置注解上的参数
            getControllerMethodDescription(joinPoint, controllerLog, operationEvent, jsonResult);
            // 设置消耗时间
            StopWatch stopWatch = KEY_CACHE.get().getStopWatch();
            stopWatch.stop();
            operationEvent.setCostTime(stopWatch.getTime());
            operationEvent.setOperationTime(new Date());
            // 发布事件保存数据库
            SpringUtils.context().publishEvent(operationEvent);
        } catch (Exception exp) {
            log.error("记录操作日志异常:{}", exp.getMessage());
        } finally {
            KEY_CACHE.remove();
        }
    }

    /**
     * 获取注解中对方法的描述信息 用于Controller层注解
     *
     * @param log          日志
     * @param operationLog 操作日志
     * @throws Exception
     */
    public void getControllerMethodDescription(JoinPoint joinPoint, OperationLog log, OperationLogEvent operationLog, Object jsonResult) throws Exception {
        // 设置action动作
        operationLog.setBusinessType(log.logBusinessType().getType());
        // 设置标题
        operationLog.setTitle(log.title());
        // 设置操作人类别
        operationLog.setOperatorType(log.operationType().getStatus());
        RequestInfoDto info = KEY_CACHE.get().getInfo();
        if (Objects.equals(StatusEnums.LOGIN_USER_TYPE_ADMIN.getStatus(), operationLog.getOperatorType())) {
            if(info != null) {
                operationLog.setOperationUser(info.getAdminUserId());
            }
        }
        // 是否需要保存request，参数和值
        if (log.isSaveRequestData()) {
            // 获取参数的信息，传入到数据库中。
            Object requestParam = getRequestParam(joinPoint);
            if (requestParam != null) {
                operationLog.setParam(StringUtils.substring(JSONUtil.toJsonStr(requestParam), 0, 2000));
            }
        }
        // 是否需要保存response，参数和值
        if (log.isSaveResponseData() && ObjectUtil.isNotNull(jsonResult)) {
            operationLog.setJsonResult(StringUtils.substring(JSONUtil.toJsonStr(jsonResult), 0, 2000));
        }
    }

    /**
     * 解析参数
     *
     * @return
     */
    private Object getRequestParam(JoinPoint pjd) {
        Object params = null;
        if (!ServletUtils.isMultipartContent()) {
            try {
                List<Object> paramList = Stream.of(pjd.getArgs())
                        .filter(args -> !(args instanceof ServletRequest))
                        .filter(args -> !(args instanceof ServletResponse))
                        .collect(Collectors.toList());
                if (paramList.size() == 1) {
                    Object data = paramList.get(0);
                    try {
                        JSONObject jsonObject = JSONUtil.parseObj(paramList.get(0));
                        if (jsonObject == null || jsonObject.isEmpty()) {
                            // json解析失败
                            return data;
                        }
                        return jsonObject;
                    } catch (Exception e) {
                        return data;
                    }
                } else {
                    return JSONUtil.parseArray(paramList);
                }
            } catch (Exception e) {
                log.error("参数解析失败", e);
            }
        }
        return params;
    }
}
